﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security;

namespace CodeRun.Util
{
    public class ExecuteProgramResult
    {
        public int ExitCode { get; set; }

        public List<string> OutputLines { get; set; }
    }

    public static class ProcessHelper
    {
        public static ExecuteProgramResult ExecuteProgramWithOutput(string filename, string arguments,
                                                                    string workingDirectory, TimeSpan? timeout)
        {
            long timestamp = DateTime.Now.Ticks;
            Trace.WriteLine(String.Format("{3}: Executing {0} {1} at {2}", filename, arguments, workingDirectory,
                                          timestamp));
            var entrance = new object();
            var result = new ExecuteProgramResult();
            DataReceivedEventHandler dataHandler = delegate(object sender, DataReceivedEventArgs e)
                                                       {
                                                           lock (entrance)
                                                           {
                                                               if (e.Data != null)
                                                                   result.OutputLines.AddRange(e.Data.Split('\n'));
                                                           }
                                                       };
            result.OutputLines = new List<string>();
            try
            {
                int? ti = null;
                if (timeout != null)
                    ti = (int) timeout.Value.TotalMilliseconds;
                result.ExitCode = ExecuteAndWaitForExit(filename, arguments, workingDirectory, dataHandler, dataHandler,
                                                        ti);
                Trace.WriteLine(String.Format("{0}: Finished with exit code {1}", timestamp, result.ExitCode));
            }
            catch (Exception e)
            {
                Trace.WriteLine(String.Format("{0}: Exception while executing program: {1}", timestamp, e.Message));
                result.ExitCode = 1;
            }
            return result;
        }

        public static int ExecuteAndWaitForExit(string filename, string arguments, string workingDirectory,
                                                DataReceivedEventHandler outputDataRecieved,
                                                DataReceivedEventHandler errorDataRecieved)
        {
            return ExecuteAndWaitForExit(filename, arguments, workingDirectory, outputDataRecieved, errorDataRecieved,
                                         null, null, null);
        }

        public static int ExecuteAndWaitForExit(string filename, string arguments, string workingDirectory,
                                                DataReceivedEventHandler outputDataRecieved,
                                                DataReceivedEventHandler errorDataRecieved, int? timeout)
        {
            return ExecuteAndWaitForExit(filename, arguments, workingDirectory, outputDataRecieved, errorDataRecieved,
                                         null, null, timeout);
        }

        public static int ExecuteAndWaitForExit(string filename, string arguments, string workingDirectory,
                                                TextWriter outputData, TextWriter outputError, int? timeout)
        {
            var entrance = new object();
            DataReceivedEventHandler d1 = null;
            if (outputData != null)
            {
                d1 = delegate(object x1, DataReceivedEventArgs e)
                         {
                             lock (entrance)
                             {
                                 outputData.Write(e.Data);
                             }
                         };
            }
            DataReceivedEventHandler d2 = null;
            if (outputError != null)
            {
                d2 = delegate(object x1, DataReceivedEventArgs e)
                         {
                             lock (entrance)
                             {
                                 outputError.Write(e.Data);
                             }
                         };
            }
            return ExecuteAndWaitForExit(filename, arguments, workingDirectory, d1, d2, null, null, timeout);
        }

        public static int ExecuteAndWaitForExit(string filename, string arguments, string workingDirectory,
                                                DataReceivedEventHandler outputDataRecieved,
                                                DataReceivedEventHandler errorDataRecieved, string username,
                                                string password, int? timeout)
        {
            var si = new ProcessStartInfo
                         {
                             FileName = filename,
                             Arguments = arguments
                         };
            var process = new Process {StartInfo = si};

            if (workingDirectory.IsNotNullOrEmpty())
                si.WorkingDirectory = workingDirectory;
            si.UseShellExecute = false;
            si.CreateNoWindow = true;
            if (username.IsNotNullOrEmpty())
            {
                si.UserName = username;
                var ss = new SecureString();
                if (password.IsNotNullOrEmpty())
                {
                    foreach (char ch in password)
                    {
                        ss.AppendChar(ch);
                    }
                }
                si.Password = ss;
            }
            if (outputDataRecieved != null)
            {
                si.RedirectStandardOutput = true;
                process.OutputDataReceived += outputDataRecieved;
            }
            if (errorDataRecieved != null)
            {
                si.RedirectStandardError = true;
                process.ErrorDataReceived += errorDataRecieved;
            }
            if (!process.Start())
                throw new Exception("could not start");
            if (outputDataRecieved != null)
                process.BeginOutputReadLine();
            if (errorDataRecieved != null)
                process.BeginErrorReadLine();
            if (timeout != null)
            {
                bool exited = process.WaitForExit(timeout.Value);
                if (!exited)
                {
                    process.Kill();
                    throw new Exception(String.Format("The process {0} {1} did not exit by the specified timeout {2}",
                                                      filename, arguments, timeout.Value));
                }
            }
            else
            {
                process.WaitForExit();
            }
            return process.ExitCode;
        }

        public static string Quote(string argument)
        {
            return "\"" + argument + "\"";
        }
    }
}